#include <iostream>
#include <string>
#include <cassert>

#include "simple/geom.hpp"
#include "simple/support/range.hpp"
#include "simple/support/algorithm.hpp"
#include "simple/support/vuple.hpp"

#include "ecs.hpp"

using namespace std::literals;
namespace support = simple::support;

using int2 = simple::geom::vector<int,2>;
using int2_range = support::range<int2>;
using int_rect = simple::geom::segment<int2>;

struct motion
{
	int2 destination;
};

enum class pixel_color : unsigned char
{ black, red, green, yellow, blue, magenta, cyan, white };
enum class pixel_intensity : unsigned char
{ normal, high, low };

struct pixel_properties
{
	pixel_color fg = pixel_color::white;
	pixel_color bg = pixel_color::black;
	pixel_intensity intensity = pixel_intensity::normal;

	constexpr bool operator==(const pixel_properties other) noexcept
	{ return fg == other.fg && bg == other.bg && intensity == other.intensity; }
	constexpr bool operator!=(const pixel_properties other) noexcept
	{ return not((*this) == other); }
};

struct pixel
{
	char c = ' ';
	pixel_properties p = {};
};

struct texture
{
	std::string sprite;
	pixel_properties tint = {};
	bool mirror = false;
	bool tile = false;
};

using ui_entities = entities<int_rect, texture, motion>;

struct ui_context
{
	const game::world* world = nullptr;
	std::vector<std::string> guy_sprites;
	ui_entities everything = {};
};


struct number_label :
	public ui_entities::metastruct
	<
		prop<"name", texture>,
		prop<"value", texture>
	>
{


	static int2_range create(ui_context* ctx, entity_id_t block_id, int2 position, std::string icon, int2 icon_size, pixel_color icon_color, int number)
	{
		int2_range bounds{position, position};
		auto icon_rect = ctx->everything.make(block_id, [&](ui_entities::components_type& c)
		{
			c.push(texture{ icon, {icon_color} });
			return c.push(int_rect{icon_size, position});
		}).release().components;
		auto count_rect = ctx->everything.make(block_id, [&](ui_entities::components_type& c)
		{
			auto& txtr = c.push(texture{ " " + std::to_string(number)});
			return c.push(int_rect{{int(txtr.sprite.size()), 1}, position + icon_rect.size * int2::i()}); // horizontal layout
		}).release().components;
		bounds.upper() = max(int2_range(icon_rect).upper(), int2_range(count_rect).upper()); // bounds
		return bounds;
	}

};

struct force_panel :
	public ui_entities::metastruct
	<
		prop<"title", texture>,
		prop<"guy_counts", metavector<number_label, 3>>
	>
{
};

class battle_ui
{
	enum { forces, grid };

	ui_context* ctx;
	support::range<entity_id_t> blocks;

	int2_range make_force(entity_id_t block_id, int2 position, const game::force& gamestate)
	{
		int2_range force_bounds{position, position};
		position += 1; // padding
		assert(gamestate.guys.size() == ctx->guy_sprites.size());
		for(auto&& [guy, guy_sprite, guy_config] : support::range{
			// TODO: support::vrange, make_vrange? zip_range??
			support::vbegin(gamestate.guys, ctx->guy_sprites, ctx->world->guy_config),
			support::vend(gamestate.guys, ctx->guy_sprites, ctx->world->guy_config)
		})
		{
			auto& [guy_count, guy_color] = guy;
			if(guy_count != game::force::counter{})
			{
				auto label_bounds = number_label::create(ctx, block_id, position, guy_sprite, guy_config.size, pixel_color(unsigned(guy_color)), int(guy_count));
				force_bounds.upper() = label_bounds.upper(); // bounds
				position += (label_bounds.upper() - label_bounds.lower()) * int2::j(); // vertical layout
			}
		}

		force_bounds.upper().x() += 1; // padding
		return force_bounds;
	}

	public:
	// FIXME: std any can't handle this... -_-
	// battle_ui(const battle_ui&) = delete;
	// battle_ui(battle_ui&&) = delete;
	// battle_ui& operator=(const battle_ui&) = delete;
	// battle_ui& operator=(battle_ui&&) = delete;
	battle_ui(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); }
	battle_ui(battle_ui&&) { assert(false && "battle_ui shouldn't move"); }
	battle_ui& operator=(const battle_ui&) { assert(false && "battle_ui shouldn't be copied"); return *this;}
	battle_ui& operator=(battle_ui&&) { assert(false && "battle_ui shouldn't move"); return *this; }

	battle_ui(int2_range layout_context, game::battle& gamestate, ui_context& ctx) : ctx(&ctx), blocks{}
	{
		// create da blocks

		blocks.lower() = this->ctx->everything.next_block_id();
		blocks.upper() = blocks.lower();

		auto pos = layout_context.lower();
		auto left_bounds = make_force(blocks.upper(), pos, gamestate.forces[0]);
		pos += (left_bounds.upper() - pos) * int2::i();

		int2 grid_size {gamestate.field_size * int2{2,1}};
		int2 grid_bounds {grid_size
			+ int2::i() // separator
		};
		auto grid_pos = pos;

		pos += grid_bounds * int2::i();

		make_force(blocks.upper(), pos, gamestate.forces[1]);
		auto grid_block_id = blocks.upper() = this->ctx->everything.next_block_id();


		this->ctx->everything.make(grid_block_id, [&grid_size, &grid_pos](ui_entities::components_type& c)
		{
			texture floor { " " };
			floor.tint.bg = pixel_color::black;
			floor.tint.intensity = pixel_intensity::low;
			floor.tile = true;

			texture wall { "|" };
			wall.tile = true;
			wall.tint.intensity = pixel_intensity::low;

			c.push(floor);
			c.push(int_rect{grid_size/int2(2,1), grid_pos});

			c.push(wall);
			c.push(int_rect{int2(1, grid_size.y()), grid_pos + int2::i()*grid_size/int2(2,1) });

			c.push(floor);
			return c.push(int_rect{grid_size/int2(2,1), grid_pos + int2::i()*grid_size/int2(2,1) + int2::i()});
		}).release();

	}

	std::size_t update(const std::vector<game::state_update>& updates)
	{
		return updates.size();
	}

	void spawned(game::field_t::iterator spawn)
	{
		// update forces
		auto ent = ctx->everything.find(blocks.lower());
		auto label = number_label::iterator{ent.begin()};
		(*label).get<"value">().sprite = "  a";
		++label;
		(*label).get<"value">().sprite = "  b";
		++label;
		(*label).get<"value">().sprite = "  c";

		// update guys
		//
		// update layouts
	}

	~battle_ui()
	{
		ctx->everything.erase_block(blocks.lower(), blocks.upper());
	}
};

struct ui_state
{
	ui_context context;
	std::vector<pixel> canvas = {};
	std::optional<battle_ui> battle = std::nullopt;
	bool running = true;
};

char mirror(char c)
{
	switch(c)
	{
		case '>': return '<'; case '<': return '>';
		case ')': return '('; case '(': return ')';
		case ']': return '['; case '[': return ']';
		case '}': return '{'; case '{': return '}';
		case '\'': return '`'; case '`': return '\'';
		case '/': return '\\'; case '\\': return '/';
		default: return c;
	}
}

ui::ui(const game::world& gameworld) : state(ui_state{
	{&gameworld, {"/"s,")"s,">"s}}
}) {}

void ui::intro(std::string title)
{
	std::cout << "\e[34m"<< title << "\e[0m" << std::endl;
	// std::cout << "\e[999;999H";
	// std::cout << "\e[6n" << std::endl;
}

void ui::battle(game::battle& battle)
{
	auto& self = std::any_cast<ui_state&>(state);
	self.battle.emplace(int2_range{}, battle, self.context);
}

std::optional<game::action> ui::action()
{
	// TODO
	auto& self = std::any_cast<ui_state&>(state);
	int from, to;
	std::cout << ">> ";
	self.running = bool(std::cin >> from >> to);

	return std::nullopt;
}

std::size_t ui::update(const std::vector<game::state_update>& updates)
{
	auto& self = std::any_cast<ui_state&>(state);
	if(self.battle) self.battle->update(updates);
	return updates.size();
}

void ui::spawned(game::field_t::iterator spawn)
{
	auto& self = std::any_cast<ui_state&>(state);
	if(self.battle) self.battle->spawned(spawn);
}

void render(ui_state& self);

bool ui::loop()
{
	auto& self = std::any_cast<ui_state&>(state);
	if(self.running)
		render(self);
	else std::cout << '\n';
	return self.running;
}

void set_pixel(std::vector<pixel>& dest, const int2 size, const int2 pos, const pixel value)
{
	assert(size.x()*size.y() == dest.size());
	assert(pos < size);
	dest[pos.y()*size.x() + pos.x()] = value;
}

pixel get_pixel(const texture& src, const int2 size, int2 pos)
{
	assert(size >= int2::zero());
	assert(pos >= int2::zero());
	assert(src.tile || size.x()*size.y() == src.sprite.size());
	assert(src.tile || pos < size);
	if(src.tile) pos = pos%size;
	auto row_pos = src.mirror ? (size.x()-1) - pos.x() : pos.x();
	auto flat = pos.y()*size.x() + row_pos;
	if(src.tile) flat %= src.sprite.size();
	auto c = src.sprite[flat];
	return {src.mirror ? mirror(c) : c, src.tint};
}

template <typename OutIt>
void write_escape_code(pixel_properties p, OutIt out)
{
	std::string prefix = "\e[";
	std::string postfix = "m";
	std::copy(prefix.begin(), prefix.end(), out);
	*out++ = '0' + static_cast<unsigned char>(p.intensity);
	*out++ = ';';
	*out++ = '4';
	*out++ = '0' + static_cast<unsigned char>(p.bg);
	*out++ = ';';
	*out++ = '3';
	*out++ = '0' + static_cast<unsigned char>(p.fg);
	std::copy(postfix.begin(), postfix.end(), out);
}

void render(ui_state& self)
{
	int2_range canvas_bounds{};
	auto all_bounds = self.context.everything.get<int_rect>();
	for(int2_range bounds : all_bounds)
	{
		canvas_bounds.lower().min(bounds.lower());
		canvas_bounds.upper().max(bounds.upper());
	}

	assert(canvas_bounds.lower() == int2::zero());
	self.canvas.resize(canvas_bounds.upper().x() * canvas_bounds.upper().y());

	auto all_textures = self.context.everything.get<texture>();
	for(auto&& [bounds, texture] : support::range{
		// TODO: support::vrange, make_vrange? zip_range??
		vbegin(all_bounds, all_textures),
		vend(all_bounds, all_textures)
	}) simple::geom::loop(bounds.size, [&](auto i)
		{ set_pixel(self.canvas, canvas_bounds.upper(), bounds.position + i, get_pixel(texture, bounds.size, i)); });

	pixel_properties pp{};
	std::ostream_iterator<char> oi (std::cout,"");
	write_escape_code(pp, oi);
	// TODO: use geom::loop
	for(int i = 0; i != canvas_bounds.upper().y(); ++i)
	{
		for(int j = 0; j != canvas_bounds.upper().x(); ++j)
		{
			auto p = self.canvas[i*canvas_bounds.upper().x() + j];
			if(pp != p.p)
			{
				pp = p.p;
				write_escape_code(pp, oi);
			}
			*oi++ = p.c;
		}
		pp = pixel_properties{};
		write_escape_code(pp, oi);
		*oi++ = '\n';
	}
	std::cout << "\e[0m" << '\n';

}
